////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.managers
{

import flash.display.DisplayObject;
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.MouseEvent;
import flash.events.ProgressEvent;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.ui.Mouse;
import mx.core.Application;
import mx.core.EventPriority;
import mx.core.FlexSprite;
import mx.core.mx_internal;
import mx.styles.CSSStyleDeclaration;
import mx.styles.StyleManager;

use namespace mx_internal;

//--------------------------------------
//  Styles
//--------------------------------------

/**
 *  The skin for the busy cursor
 *
 *  @default mx.skins.halo.BusyCursor
 */
[Style(name="busyCursor", type="Class", inherit="no")]

/**
 *  The CursorManager class controls a prioritized list of cursors,
 *  where the cursor with the highest priority is currently visible.
 *  If the cursor list contains more than one cursor with the same priority,
 *  the Cursor Manager displays the most recently created cursor.
 *  
 *  <p>For example, if your application performs processing
 *  that requires the user to wait until the processing completes,
 *  you can change the cursor so that it reflects the waiting period.
 *  In this case, you can change the cursor to an hourglass
 *  or other image.</p>
 *  
 *  <p>You also might want to change the cursor to provide feedback
 *  to the user to indicate the actions that the user can perform.
 *  For example, you can use one cursor image to indicate that user input
 *  is enabled, and another to indicate that input is disabled. 
 *  You can use a JPEG, GIF, PNG, or SVG image, a Sprite object, or a SWF file
 *  as the cursor image.</p>
 *  
 *  <p>All methods and properties of the CursorManager are static,
 *  so you do not need to create an instance of it.</p>
 *
 *  @see mx.managers.CursorManagerPriority
 */
public class CursorManager
{
    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constant that is the value of <code>currentCursorID</code> property
	 *  when there is no cursor managed by the CursorManager and therefore
	 *  the system cursor is being displayed.
	 */
	public static const NO_CURSOR:int = 0;
	
	//--------------------------------------------------------------------------
	//
	//  Class variables
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static var nextCursorID:int = 1;
	
	/**
	 *  @private
	 */
	private static var cursorList:Array = [];
	
	/**
	 *  @private
	 */
	private static var busyCursorList:Array = [];
	
	/**
	 *  @private
	 */
	private static var initialized:Boolean = false;
	
	/**
	 *  @private
	 */
	private static var cursorHolder:Sprite;
	
	/**
	 *  @private
	 */
	private static var currentCursor:DisplayObject;

	/**
	 *  @private
	 */
	private static var overTextField:Boolean = false;
	
	/**
	 *  @private
	 */
	private static var overLink:Boolean = false;
	
	/**
	 *  @private
	 */
	private static var showSystemCursor:Boolean = false;
	
	/**
	 *  @private
	 */
	private static var showCustomCursor:Boolean = false;
	
	/**
	 *  @private
	 */
	private static var systemManager:SystemManager = null;
	
	/**
	 *  @private
	 */
	private static var sourceArray:Array = [];

	//--------------------------------------------------------------------------
	//
	//  Class properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  currentCursorID
	//----------------------------------

	/**
	 *  ID of the current custom cursor,
	 *  or NO_CURSOR if the system cursor is showing.
	 */
	public static var currentCursorID:int = NO_CURSOR;

	//----------------------------------
	//  currentCursorXOffset
	//----------------------------------

	/**
	 *  The x offset of the custom cursor, in pixels,
	 *  relative to the mouse pointer.
	 *  	 
	 *  @default 0
	 */
	public static var currentCursorXOffset:Number = 0;

	//----------------------------------
	//  currentCursorYOffset
	//----------------------------------

	/**
	 *  The y offset of the custom cursor, in pixels,
	 *  relative to the mouse pointer.
	 *
	 *  @default 0
	 */
	public static var currentCursorYOffset:Number = 0;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Makes the cursor visible.
	 *  Cursor visibility is not reference-counted.
	 *  A single call to the <code>showCursor()</code> method
	 *  always shows the cursor regardless of how many calls
	 *  to the <code>hideCursor()</code> method were made.
	 */
	public static function showCursor():void
	{
		cursorHolder.visible = true;
	}
	
	/**
	 *  Makes the cursor invisible.
	 *  Cursor visibility is not reference-counted.
	 *  A single call to the <code>hideCursor()</code> method
	 *  always hides the cursor regardless of how many calls
	 *  to the <code>showCursor()</code> method were made.
	 */
	public static function hideCursor():void
	{
		cursorHolder.visible = false;
	}

	/**
	 *  Creates a new cursor and sets an optional priority for the cursor.
	 *  Adds the new cursor to the cursor list.
	 *
	 *  @param cursorClass Class of the cursor to display.
	 *
	 *  @param priority Integer that specifies
	 *  the priority level of the cursor.
	 *  Possible values are <code>CursorManagerPriority.HIGH</code>,
	 *  <code>CursorManagerPriority.MEDIUM</code>, and <code>CursorManagerPriority.LOW</code>.
	 *
	 *  @param xOffset Number that specifies the x offset
	 *  of the cursor, in pixels, relative to the mouse pointer.
	 *
	 *  @param yOffset Number that specifies the y offset
	 *  of the cursor, in pixels, relative to the mouse pointer.
	 *
	 *  @return The ID of the cursor.
	 *
	 *  @see mx.managers.CursorManagerPriority
	 */
	public static function setCursor(cursorClass:Class, priority:int = 2,
									 xOffset:Number = 0,
									 yOffset:Number = 0):int 
	{
		/*
		if (!cursorHolder._target)
		{
			// We may have been reloaded by a shell so reset everything.
			currentCursorID = NO_CURSOR;
			nextCursorID = 1;
			cursorList = [];
			busyCursorList = [];
			initialized = false;
			overTextField = false;
			overLink = false;
		}
		*/
		
		var cursorID:int = nextCursorID++;
		
		// Create a new CursorQueueItem.
		var item:CursorQueueItem = new CursorQueueItem();
		item.cursorID = cursorID;
		item.cursorClass = cursorClass;
		item.priority = priority;
		item.x = xOffset;
		item.y = yOffset;
		
		// Push it onto the cursor list.
		cursorList.push(item);
		
		// Re-sort the cursor list based on priority level.
		cursorList.sort(priorityCompare);

		// Determine which cursor to display
		showCurrentCursor();
		
		return cursorID;
	}
	
	/**
	 *  @private
	 */
	private static function priorityCompare(a:CursorQueueItem, b:CursorQueueItem):int
	{
		if (a.priority < b.priority)
			return -1;
		else if (a.priority == b.priority)
			return 0;
		
		return 1;
	}

	/**
	 *  Removes a cursor from the cursor list.
	 *  If the cursor being removed is the currently displayed cursor,
	 *  the CursorManager displays the next cursor in the list, if one exists.
	 *  If the list becomes empty, the CursorManager displays
	 *  the default system cursor.
	 *
	 *  @param cursorID ID of cursor to remove.
	 */
	public static function removeCursor(cursorID:int):void 
	{
		for (var i:Object in cursorList)
		{
			var item:CursorQueueItem = cursorList[i];
			if (item.cursorID == cursorID)
			{
				// Remove the element from the array.
				cursorList.splice(i, 1); 

				// Determine which cursor to display.
				showCurrentCursor();
					
				break;
			}
		}
	}
	
	/**
	 *  Removes all of the cursors from the cursor list
	 *  and restores the system cursor.
	 */
	public static function removeAllCursors():void
	{
		cursorList.splice(0);
		
		showCurrentCursor();
	}

	/**
	 *  Displays the busy cursor.
	 *  The busy cursor has a priority of CursorManagerPriority.LOW.
	 *  Therefore, if the cursor list contains a cursor
	 *  with a higher priority, the busy cursor is not displayed 
	 *  until you remove the higher priority cursor.
	 *  To create a busy cursor at a higher priority level,
	 *  use the <code>setCursor()</code> method.
	 */
	public static function setBusyCursor():void 
	{
		var cursorManagerStyleDeclaration:CSSStyleDeclaration =
			StyleManager.getStyleDeclaration("CursorManager");
		
		var busyCursorClass:Class =
			cursorManagerStyleDeclaration.getStyle("busyCursor");
		
		busyCursorList.push(setCursor(busyCursorClass, CursorManagerPriority.LOW));
	}

	/**
	 *  Removes the busy cursor from the cursor list.
	 *  If other busy cursor requests are still active in the cursor list,
	 *  which means you called the <code>setBusyCursor()</code> method more than once,
	 *  a busy cursor does not disappear until you remove
	 *  all busy cursors from the list.
	 */
	public static function removeBusyCursor():void 
	{
		if (busyCursorList.length > 0)
			removeCursor(int(busyCursorList.pop()));
	}

	/**
	 *  @private
	 *  Decides what cursor to display.
	 */
	private static function showCurrentCursor():void 
	{
		if (systemManager == null)
			systemManager = Application.application.systemManager;
			
		if (cursorList.length > 0)
		{
			if (!initialized)
			{
				// The first time a cursor is requested of the CursorManager,
				// create a Sprite to hold the cursor symbol
				cursorHolder = new FlexSprite();
				cursorHolder.name = "cursorHolder";
				cursorHolder.mouseEnabled = false;
				systemManager.cursorChildren.addChild(cursorHolder);

				initialized = true;
			}

			// Get the top most cursor.
			var item:CursorQueueItem = cursorList[0];
				
			// If the system cursor was being displayed, hide it.
			if (currentCursorID == NO_CURSOR)
				Mouse.hide();

			// If the current cursor has changed...
			if (item.cursorID != currentCursorID)
			{
				if (cursorHolder.numChildren > 0)
					cursorHolder.removeChildAt(0);
				
				currentCursor = new item.cursorClass(); 
				
				if (currentCursor)
				{
					if (currentCursor is InteractiveObject)
						InteractiveObject(currentCursor).mouseEnabled = false;
					cursorHolder.addChild(currentCursor);
					cursorHolder.x = systemManager.mouseX + item.x;
					cursorHolder.y = systemManager.mouseY + item.y;
					systemManager.stage.addEventListener(MouseEvent.MOUSE_MOVE,
												   mouseMoveHandler,true,EventPriority.CURSOR_MANAGEMENT);
				}
			
				currentCursorID = item.cursorID;
				currentCursorXOffset = item.x;
				currentCursorYOffset = item.y;
			}
		} 
		else 
		{
			if (currentCursorID != NO_CURSOR)
			{
				// There is no cursor in the cursor list to display,
				// so cleanup and restore the system cursor.
				currentCursorID = NO_CURSOR;
				currentCursorXOffset = 0;
				currentCursorYOffset = 0;
				systemManager.stage.removeEventListener(MouseEvent.MOUSE_MOVE,
												  CursorManager.mouseMoveHandler,true);
				cursorHolder.removeChild(currentCursor);
			}
			Mouse.show();
		}
	}
	
	/**
	 *  @private
	 *  Called by other components if they want to display
	 *  the busy cursor during progress events.
	 */
	mx_internal static function registerToUseBusyCursor(source:Object):void
	{
		if (source && source is EventDispatcher) 
		{
			source.addEventListener(ProgressEvent.PROGRESS, CursorManager.progressHandler);
			source.addEventListener(Event.COMPLETE, CursorManager.completeHandler);
			source.addEventListener(IOErrorEvent.IO_ERROR, CursorManager.completeHandler);
		}
	}

	/**
	 *  @private
	 *  Called by other components to unregister
	 *  a busy cursor from the progress events.
	 */
	mx_internal static function unRegisterToUseBusyCursor(source:Object):void
	{
		if (source && source is EventDispatcher) 
		{
			source.removeEventListener(ProgressEvent.PROGRESS, CursorManager.progressHandler);
			source.removeEventListener(Event.COMPLETE, CursorManager.completeHandler);
			source.removeEventListener(IOErrorEvent.IO_ERROR, CursorManager.completeHandler);
		}
	}
	
	/**
	 *  @private
	 */
	private static function findSource(target:Object):int
	{
		var n:int = sourceArray.length;
		for (var i:int = 0; i < n; i++)
		{
			if (sourceArray[i] === target)
				return i;
		}
		return -1;
	}

	//--------------------------------------------------------------------------
	//
	//  Class event handlers
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	private static function mouseMoveHandler(event:MouseEvent):void
	{
		//trace("mouseMove target", event.target);
		//trace("mouseMove x", event.localX, "y", event.localY,
		//			  "root=cursorHolder?" ,rootApplication === cursorHolder);

		cursorHolder.x = systemManager.mouseX + currentCursorXOffset;
		cursorHolder.y = systemManager.mouseY + currentCursorYOffset;
		
		var target:Object = event.target;
		
		// Do target test.
		if (!overTextField &&
			target is TextField && target.type == TextFieldType.INPUT)
		{	
			overTextField = true;
			showSystemCursor = true;
		} 
		else if (overTextField &&
				 !(target is TextField && target.type == TextFieldType.INPUT))
		{
			overTextField = false;
			showCustomCursor = true;
		}
		
		// Handle switching between system and custom cursor.
		if (showSystemCursor)
		{
			showSystemCursor = false;
			cursorHolder.visible = false;
			Mouse.show();
		}
		if (showCustomCursor)
		{
			showCustomCursor = false;
			cursorHolder.visible = true;
			Mouse.hide();
		}
	}
	
	/**
	 *  @private
	 *  Displays the busy cursor if a component is in a busy state.
	 */
	private static function progressHandler(event:ProgressEvent):void
	{
		// Only pay attention to the first progress call. Ignore all others.
		var sourceIndex:int = findSource(event.target);
		if (sourceIndex == -1)
		{
			// Add the target to the list of objects we are listening for.
			sourceArray.push(event.target);
			
			CursorManager.setBusyCursor();
		}
	}
	
	/**
	 *  @private
	 */
	private static function completeHandler(event:Event):void
	{
		var sourceIndex:int = findSource(event.target);
		if (sourceIndex != -1)
		{
			// Remove from the list of targets we are listening to.
			sourceArray.splice(sourceIndex, 1);
			
			CursorManager.removeBusyCursor();
		}
	}

}

}

////////////////////////////////////////////////////////////////////////////////
//
//  Helper class: CursorManager
//
////////////////////////////////////////////////////////////////////////////////

import mx.managers.CursorManager;
import mx.managers.CursorManagerPriority;

/**
 *  @private
 */
class CursorQueueItem
{
	include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------
	
	/**
	 *  Constructor.
	 */
	public function CursorQueueItem()
	{
		super();
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	public var cursorID:int = CursorManager.NO_CURSOR;

	/**
	 *  @private
	 */
	public var cursorClass:Class = null;

	/**
	 *  @private
	 */
	public var priority:int = CursorManagerPriority.MEDIUM;

	/**
	 *  @private
	 */
	public var x:Number;

	/**
	 *  @private
	 */
	public var y:Number;
}
